package httpd

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"sync"
	"time"

	"gitee.com/asphodelus_dev/go-s3-local/fs"
	"gitee.com/asphodelus_dev/go-s3-local/util"
	"github.com/google/uuid"
)

type Service struct {
	mu      sync.RWMutex
	DataDir string
}

func NewService(dir string) *Service {
	bucketPath := path.Join(dir, basicPathSuffix)
	if !util.IsPathExist(bucketPath) {
		os.MkdirAll(bucketPath, 0o755)
	}
	return &Service{
		DataDir: dir,
	}
}

func (f *Service) CreateBucket(bucketName string) (Bucket, error) {
	f.mu.Lock()
	defer f.mu.Unlock()

	bucketPath := path.Join(f.DataDir, basicPathSuffix, bucketName)
	err := os.MkdirAll(bucketPath, 0o755)
	if err != nil {
		return Bucket{}, err
	}
	bucket := Bucket{}
	bucket.Name = bucketName
	bucket.CreationDate = util.DateFormatToSecond(time.Now())
	return bucket, nil
}

func (f *Service) DeleteBucket(bucketName string) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	bucketPath := path.Join(f.DataDir, basicPathSuffix, bucketName)
	err := os.RemoveAll(bucketPath)
	// TODO 删除分片
	if err != nil {
		return err
	}

	return nil
}

func (f *Service) DeleteObject(bucketName string, objectKey string) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	objectBasicDir := path.Join(f.DataDir, basicPathSuffix, bucketName, objectKey)
	err := os.RemoveAll(objectBasicDir)
	// TODO 删除分片
	if err != nil {
		return err
	}
	return nil
}

func (f *Service) PutObject(bucketName string, objectKey string, data []byte) error {
	f.mu.Lock()
	defer f.mu.Unlock()

	filePath := path.Join(f.DataDir, basicPathSuffix, bucketName, objectKey)
	if filePath[len(filePath)-1:] == "/" {
		os.MkdirAll(filePath, os.ModePerm)
	} else {
		fileName := filepath.Base(objectKey)
		reader := bytes.NewReader(data)
		err := util.SaveFileWithMetaInfo(filePath, http.DetectContentType(data), fileName, int64(len(data)), time.Now(), reader)
		if err != nil {
			return err
		}
	}
	return nil
}

func (f *Service) CopyObject(sourceBucketName string, sourceObjectKey string, targetBucketName string, targetObjectKey string) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	srcMetaFilePath := path.Join(f.DataDir, basicPathSuffix, sourceBucketName, sourceObjectKey+".meta")
	destMetaFilePath := path.Join(f.DataDir, basicPathSuffix, targetBucketName, targetObjectKey+".meta")
	return util.CopyFile(srcMetaFilePath, destMetaFilePath)
}

func (f *Service) InitiateMultipartUpload(bucketName string) (string, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	tmpDir := path.Join(f.DataDir, "tmp")
	bd := path.Join(f.DataDir, basicPathSuffix, bucketName)
	err := os.MkdirAll(bd, os.ModePerm)
	if err != nil {
		return "", err
	}
	guid := uuid.New()
	uploadId := guid.String()
	tmpPath := path.Join(tmpDir, uploadId)
	err = os.MkdirAll(tmpPath, os.ModePerm)
	if err != nil {
		return "", err
	}

	return guid.String(), nil
}

func (f *Service) UploadPart(bucketName string, objectKey string, partNumber string, uploadId string, data []byte) (string, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	tmpDir := path.Join(f.DataDir, "tmp")
	// 将分片上传到临时路径
	tmpPartFilePath := path.Join(tmpDir, uploadId, partNumber+".temp")
	if !util.IsPathExist(tmpPartFilePath) {
		partFilePath := path.Join(f.DataDir, basicPathSuffix, bucketName, objectKey, partNumber+".part")
		err := util.SaveChunkFile(partFilePath, data)
		if err != nil {
			return "", err
		}
		des, _ := util.EncryptByDES(partFilePath)
		err = util.SaveChunkFile(tmpPartFilePath, util.StringToBytes(des))
	}
	etag, _ := os.ReadFile(tmpPartFilePath)

	return util.BytesToString(etag), nil
}

type byPartNumber []PartETag

func (b byPartNumber) Len() int {
	return len(b)
}

func (b byPartNumber) Less(i, j int) bool {
	return b[i].PartNumber < b[j].PartNumber
}

func (b byPartNumber) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}

func (f *Service) CompleteMultipartUpload(bucketName string, objectKey string, uploadId string, partEtags []PartETag) (string, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	// 文件最终合并位置
	mergeFilePath := path.Join(f.DataDir, basicPathSuffix, bucketName, objectKey)
	fileName := filepath.Base(mergeFilePath)
	if !util.IsPathExist(mergeFilePath) {
		mergeFile, err := os.Create(mergeFilePath)
		if err != nil {
			log.Printf("创建合并文件失败：%s\n", err)
			return "", err
		}
		check := true
		tmpDir := path.Join(f.DataDir, "tmp")
		// 校验参数里的etags是否全部正确
		for _, partETag := range partEtags {
			tmpPartFIlePath := path.Join(tmpDir, uploadId, strconv.Itoa(partETag.PartNumber)+".temp")
			etag, err := os.ReadFile(tmpPartFIlePath)
			if err != nil {
				return "", err
			}
			if partETag.ETag != util.BytesToString(etag) {
				check = false
				break
			}
		}
		if check {
			sort.Sort(byPartNumber(partEtags))
			for _, partETag := range partEtags {
				// 解码获取分片位置
				filePath, err := util.DecryptByDES(partETag.ETag)
				if err != nil {
					log.Println("解码失败")
					return "", err
				}
				file, err := os.Open(filePath)
				if err != nil {
					log.Println("文件打开失败")
					return "", err
				}
				defer file.Close()
				reader := bufio.NewReader(file)
				buf := make([]byte, 5<<20)
				for {
					n, err := reader.Read(buf)
					if err != nil && err != io.EOF {
						log.Printf("读取文件失败：%s\n", err)
						break
					}
					if n == 0 {
						break
					}
					_, err = mergeFile.Write(buf[:n])
					if err != nil {
						log.Printf("写入合并文件失败：%s\n", err)
						break
					}
				}
			}
			mergeFile.Close()
			originContent, err := os.Open(mergeFilePath)
			defer originContent.Close()
			if err != nil {
				return "", err
			}
			chunks, err := fs.SplitFile(originContent, 8<<20)
			if err != nil {
				return "", err
			}
			fileStat, err := os.Stat(mergeFilePath)
			if err != nil {
				return "", err
			}
			fileType, err := util.FileTypeFromMetaInfo(mergeFilePath)
			if err != nil {
				return "", err
			}
			var metadata fs.Metadata
			metadata.Name = fileName
			metadata.Size = fileStat.Size() // 假设文件只有一个分片
			metadata.Type = fileType        // 根据文件扩展名确定文件类型
			metadata.Time = fileStat.ModTime()
			metadata.Chunks = chunks
			metaFilePath := mergeFilePath + ".meta"
			err = fs.SaveMetadata(metaFilePath, &metadata)
			if err != nil {
				return "", err
			}
			err = os.RemoveAll(mergeFilePath)
			if err != nil {
				return "", err
			}
		}
	}
	eTag := util.EncryptByMD5(fmt.Sprintf("%s/%s", bucketName, objectKey))

	return eTag, nil
}
